/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor.ext;
import java.util.Arrays;
import java.util.ArrayList;
import org.netbeans.editor.Syntax;
/**
* Composition of several syntaxes together. There are several different
* situations in which this class can be used efficiently:
* 1) Syntax that wants to use some other syntax internally for
* recognizing one or more tokens. Example is java analyzer that would
* like to use html-syntax for detail parsing block comments.
*
* 2) Master syntax that will manage two or more slave syntaxes. Example is the
* master syntax managing java-syntax and html-syntax. The result would
* be the same like in the previous example but it's more independent.
*
* 3) Master syntax that handles switching of the two or more other syntaxes. Only one
* slave syntax is active at one time.
*
* 4) An aribitrary combination and nesting of the previous examples.
*
* The multi-syntax implements several aproaches to let everything work
* as expected:
* 1) Token ID shifting. In order to distinguish the tokens from different syntaxes,
* all the token-ids (except the system ones that are lower than zero)
* of the particular slave syntax are shifted. The token-ids of the master syntax
* have no shift. The first registered slave syntax has the token-id-shift
* equal to the master syntax's <tt>getHighestTokenID() + 1</tt>.
* The token-id-shift of each next registered slave syntax
* is the shift of the previously registered one plus
* <tt>getHighestTokenID() + 1</tt>.
* Although it could seem logically correct to merge some tokenIDs from
* different syntaxes (with the same meaning) into one tokenID, this must NOT
* be done! The obtained token-id must NOT be changed at all! Otherwise
* the <tt>translateTokenID()</tt> method would stop working correctly.
*
* 2) Correct resolving of the token names. The token-id-shifting enables
* to identify precisely from which syntax the token comes from and call
* the appropriate <tt>getTokenName()</tt> method of the particular slave
* syntax.
*
* 3) Assigning different token names to some token-ids of some slave syntaxes.
* This is needed to enable different coloring of the tokens from different slave
* syntaxes having the same name for example. It's done by calling
* <tt>changeTokenName()</tt>.
*
* 4) The <tt>translateTokenID()</tt> provides 'unshifting' of the 'global'
* token-id to the original token-id defined in the particular syntax.
*
*
* @author Miloslav Metelka
* @version 1.00
*/
public class MultiSyntax extends Syntax {
private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/** Slave syntaxes that can be used for scanning. They can
* be added by registerSyntax().
*/
private SyntaxInfo slaveSyntaxChain;
/** Last chain member of the slaveSyntaxChain */
private SyntaxInfo slaveSyntaxChainEnd;
/** IDs of all the tokens with the changed name. */
private int[] changedTokenIDs = EMPTY_INT_ARRAY;
/** Changed token names in the same order like changedTokenIDs array. */
private String[] changedTokenNames = EMPTY_STRING_ARRAY;
/** Register a particular slave syntax. */
protected void registerSyntax(Syntax slaveSyntax) {
// Compute current token-id shift
int tokenIDShift;
if (slaveSyntaxChainEnd != null) {
tokenIDShift = slaveSyntaxChainEnd.tokenIDShift
+ slaveSyntaxChainEnd.syntax.getHighestTokenID() + 1;
} else { // no slave syntaxes - take shift of the master syntax
tokenIDShift = getHighestTokenID() + 1;
}
slaveSyntaxChainEnd = new SyntaxInfo(slaveSyntax, tokenIDShift, slaveSyntaxChainEnd);
if (slaveSyntaxChain == null) {
slaveSyntaxChain = slaveSyntaxChainEnd;
}
}
/** Change token name for particular tokenID in some slave syntax. This
* method can be called AFTER all the slave syntaxes were registered.
* @param slaveSyntax slave syntax to which the tokenID belongs. Master syntax
* token-ids can't be shifted.
* @param tokenID tokenID for which the token name should be changed
* @param tokenName changed token name
*/
protected void changeTokenName(Syntax slaveSyntax, int tokenID, String tokenName) {
// Find a token shift for the
SyntaxInfo syntaxItem = slaveSyntaxChain;
while (syntaxItem != null) {
if (syntaxItem.syntax == slaveSyntax) {
tokenID += syntaxItem.tokenIDShift;
break;
}
syntaxItem = syntaxItem.next;
}
// Update the arrays
int[] ctisa = new int[changedTokenIDs.length + 1];
String[] ctnsa = new String[changedTokenNames.length + 1];
addAndSortByNumber(tokenID, tokenName, changedTokenIDs, changedTokenNames,
ctisa, ctnsa);
changedTokenIDs = ctisa;
changedTokenNames = ctnsa;
}
/** Store state of this analyzer into given mark state. */
public void storeState(StateInfo stateInfo) {
super.storeState(stateInfo);
((MultiStateInfo)stateInfo).store(this);
}
public void loadInitState() {
super.loadInitState();
SyntaxInfo syntaxItem = slaveSyntaxChain;
while (syntaxItem != null) {
syntaxItem.syntax.loadInitState();
syntaxItem = syntaxItem.next;
}
}
public void load(StateInfo stateInfo, char buffer[], int offset, int len, boolean lastBuffer) {
((MultiStateInfo)stateInfo).load(this, buffer, offset, len, lastBuffer);
super.load(stateInfo, buffer, offset, len, lastBuffer);
}
public void setStopOffset(int stopOffset) {
super.setStopOffset(stopOffset);
SyntaxInfo syntaxItem = slaveSyntaxChain;
while (syntaxItem != null) {
syntaxItem.syntax.setStopOffset(stopOffset);
}
}
public void setLastBuffer(boolean lastBuffer) {
super.setLastBuffer(lastBuffer);
SyntaxInfo syntaxItem = slaveSyntaxChain;
while (syntaxItem != null) {
syntaxItem.syntax.setLastBuffer(lastBuffer);
}
}
public StateInfo createStateInfo() {
return new MultiStateInfo();
}
/** Compare state of this analyzer to given state info. The basic
* implementation does the following:
* 1. state info of the main syntax is compared
* 2. if the result is EQUAL_STATE then go through all the registered slave syntaxes:
* a) get the info
*/
public int compareState(StateInfo stateInfo) {
int diff = super.compareState(stateInfo);
if (diff == EQUAL_STATE) {
diff = ((MultiStateInfo)stateInfo).compare(this);
}
return diff;
}
public String getTokenName(int tokenID) {
// First test the changed token-ids by binary search
int low = 0;
int high = changedTokenIDs.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
long midVal = changedTokenIDs[mid];
if (midVal < tokenID) {
low = mid + 1;
} else if (midVal > tokenID) {
high = mid - 1;
} else {
return changedTokenNames[mid];
}
}
// Search for the shifted IDs
SyntaxInfo syntaxItem = slaveSyntaxChainEnd;
while (syntaxItem != null) {
if (tokenID >= syntaxItem.tokenIDShift) {
return syntaxItem.syntax.getTokenName(tokenID - syntaxItem.tokenIDShift);
}
}
return super.getTokenName(tokenID);
}
/** Add the number together with object to the arrays of ints and objects. */
private void addAndSortByNumber(int numToAdd, Object objToAdd,
int[] numArray, Object[] objArray, int[] targetNumArray, Object[] targetObjArray) {
ArrayList arl = new ArrayList();
// Add all array members to the list
for (int i = 0; i < numArray.length; i++) {
arl.add(new NumberAndObject(numArray[i], objArray[i]));
}
arl.add(new NumberAndObject(numToAdd, objToAdd)); // add the new member to the list
// Convert list to array to sort it
NumberAndObject[] naoa = new NumberAndObject[arl.size()];
arl.toArray(naoa);
Arrays.sort(naoa); // sort array by number
// Assign the target arrays
for (int i = 0; i < naoa.length; i++) {
targetNumArray[i] = naoa[i].number;
targetObjArray[i] = naoa[i].object;
}
}
/** Class that can contain any number of the additional state infos from
* other syntaxes. The state infos stored are identified
* by the their syntax classes.
*/
static class MultiStateInfo extends BaseStateInfo {
private ChainItem stateInfoChain;
/** Goes through all the syntaxes and inits them. If the multi-state-info has
* valid state-info for the given syntax the state-info is used.
* Otherwise the syntax is inited to the init state.
*/
void load(MultiSyntax masterSyntax, char[] buffer, int offset, int len, boolean lastBuffer) {
SyntaxInfo syntaxItem = masterSyntax.slaveSyntaxChain;
while (syntaxItem != null) {
StateInfo loadInfo = null;
int masterOffsetDelta = 0;
Syntax s = syntaxItem.syntax;
if (syntaxItem.active) {
Class sc = s.getClass();
ChainItem item = stateInfoChain;
while (item != null) {
if (item.syntaxClass == sc && item.valid) {
loadInfo = item.stateInfo;
masterOffsetDelta = item.masterOffsetDelta;
break;
}
item = item.prev;
}
}
s.load(loadInfo, buffer, offset + masterOffsetDelta,
len - masterOffsetDelta, lastBuffer);
syntaxItem = syntaxItem.next;
}
}
void store(MultiSyntax masterSyntax) {
// Invalidate all state-info chain items
ChainItem item = stateInfoChain;
while (item != null) {
item.valid = false;
item = item.prev;
}
// Go through active syntaxes and store their info and master-offset
SyntaxInfo syntaxItem = masterSyntax.slaveSyntaxChain;
while (syntaxItem != null) {
if (syntaxItem.active) {
Syntax s = syntaxItem.syntax;
Class sc = s.getClass();
item = stateInfoChain;
while (item != null) {
if (item.syntaxClass == sc) { // found right item
break;
}
item = item.prev;
}
if (item == null) { // not found, add new
item = stateInfoChain = new ChainItem(s.createStateInfo(),
sc, stateInfoChain);
}
// Store the state and compute masterOffsetDelta
s.storeState(item.stateInfo);
item.masterOffsetDelta = s.getOffset() - masterSyntax.getOffset();
item.valid = true;
}
syntaxItem = syntaxItem.next;
}
}
int compare(MultiSyntax masterSyntax) {
int ret = Syntax.EQUAL_STATE;
// Go through valid state-info chain items
ChainItem item = stateInfoChain;
while (item != null && ret == Syntax.EQUAL_STATE) {
if (item.valid) {
Class sc = item.syntaxClass;
SyntaxInfo syntaxItem = masterSyntax.slaveSyntaxChain;
while (syntaxItem != null) {
if (syntaxItem.syntax.getClass() == sc) {
if (syntaxItem.active) {
ret = syntaxItem.syntax.compareState(item.stateInfo);
} else { // syntax not active but should be
ret = Syntax.DIFFERENT_STATE;
}
break;
}
syntaxItem = syntaxItem.next;
}
}
item = item.prev;
}
return ret;
}
static class ChainItem {
/** Whether this item is valid. It can become invalid if the syntax that it represents
* becomes inactive in this item.
*/
boolean valid;
/** State info of the particular slave syntax */
StateInfo stateInfo;
/* Delta of the offset variable of the slave syntax against the offset
* variable of the master syntax.
*/
int masterOffsetDelta;
/** Class of the syntax this info is for */
Class syntaxClass;
/** Previous chain item in the list */
ChainItem prev;
ChainItem(StateInfo stateInfo, Class syntaxClass, ChainItem prev) {
this.stateInfo = stateInfo;
this.syntaxClass = syntaxClass;
this.prev = prev;
}
}
}
/** Extended info about one slave syntax */
static class SyntaxInfo {
SyntaxInfo(Syntax syntax, int tokenIDShift, SyntaxInfo prevChainEnd) {
this.syntax = syntax;
this.tokenIDShift = tokenIDShift;
if (prevChainEnd != null) {
prev = prevChainEnd;
prevChainEnd.next = this;
}
}
/** The slave syntax itself */
Syntax syntax;
/** shift of the token IDs for this slave syntax */
int tokenIDShift;
/** Whether this syntax is actively scanning the text. There can be possibly more
* syntaxes scanning the in a nested way.
*/
boolean active;
/** Next member in the chain */
SyntaxInfo next;
/** Previous member in the chain */
SyntaxInfo prev;
}
/** Helper class to sort the changedTokenIDs and changedTokenNames arrays. */
static class NumberAndObject implements Comparable {
int number;
Object object;
NumberAndObject(int number, Object object) {
this.number = number;
this.object = object;
}
public int hashCode() {
return number;
}
public boolean equals(Object o) {
return (compareTo(o) == 0);
}
public int compareTo(Object o) {
if (o instanceof NumberAndObject) {
return number - ((NumberAndObject)o).number;
}
return -1;
}
}
}
/*
* Log
* 2 Gandalf 1.1 1/13/00 Miloslav Metelka Localization
* 1 Gandalf 1.0 12/28/99 Miloslav Metelka
* $
*/